#include<stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

#include "button.h"

TaskHandle_t button_task; // 任务1结构对象
TimerHandle_t button_timer;
static QueueHandle_t button_queue;
static const char *TAG = "button";
static int gpio_nums[] = {19, 21};

// 初始化 GPIO
static void configure_gpio(int gpio_num)
{
    gpio_config_t gpio_conf;
    gpio_conf.intr_type = GPIO_INTR_DISABLE;
    gpio_conf.mode = GPIO_MODE_INPUT;
    gpio_conf.pin_bit_mask = (1ULL << gpio_num);
    gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;// active level is 1.
    gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE; 

    gpio_config(&gpio_conf);
    printf("%s: init gpio[%d]\r\n",TAG, gpio_num);
}

/**
 * 获取哪一个按键按下
 */
static Button_Id get_pressed_button_id(void) {
    uint8_t state;
    Button_Id buttonId = ButtonNone;


    state = gpio_get_level(gpio_nums[0]);
    if (state == 1) {
        buttonId = ButtonStartStop;
    }

    state = gpio_get_level(gpio_nums[1]);
    if (state == 1) {
        buttonId = Button1;
    }

    return buttonId;
}

// 定时扫描，通过状体机机制进行重复确认
static void button_timer_callback(TimerHandle_t timer)
{
    static Button_Id prePressedButton = ButtonNone;
    static enum {
        NO_DOWN,
        RECHECK_DOWN,
        CONFIRMED_DOWN,
        RECHECK_UP
    } scanState = NO_DOWN;

    // 读取当前硬件状态
    Button_Id currPressedButton = get_pressed_button_id();

    switch (scanState) {
        case NO_DOWN:
            // 有按键按下，进入第1次确认检查状态
            if (currPressedButton != ButtonNone) {
                scanState = RECHECK_DOWN;
                DEBUG_PRINT("NO_DOWN -> RECHECK_DOWN:%d\n", currPressedButton);
            }
            break;
        case RECHECK_DOWN:
            if (currPressedButton == ButtonNone) {
                scanState = NO_DOWN;
                DEBUG_PRINT("RECHECK_DOWN -> NO_DOWN:%d\n", currPressedButton);
            } else if (prePressedButton == currPressedButton) {
                // 发消息通知，根据按键类型，决定如何处理. 可以考虑从某个配置表中查，这样可配置多个按键事件的通知行为
                if(currPressedButton == ButtonStartStop) {
                    if (xQueueSendToFront(button_queue, (void *)&currPressedButton, 0) != pdTRUE) {
                        printf("%s: failed send\n", TAG);
                    }
                } else {
                    if (xQueueSendToBack(button_queue, (void *)&currPressedButton, 0) != pdTRUE) {
                        printf("%s: failed send\n", TAG);
                    }
                }

                scanState = CONFIRMED_DOWN;
                DEBUG_PRINT("RECHECK_DOWN -> CONFIRMED_DOWN:%d\n", currPressedButton);
            }
            break;
        case CONFIRMED_DOWN:
            // 进入这个状态后，要做的是检查按键是否重复按下或者等待按键释放.这里简单起见，等待按键释放
            if (currPressedButton == ButtonNone) {
                // 发现按键释放？进一步确认。但这里要return，否则prePressedButton会在后面设置None
                scanState = RECHECK_UP;
                DEBUG_PRINT("CONFIRMED_DOWN -> RECHECK_UP:%d\n", currPressedButton);
                return;
            } else if (currPressedButton != prePressedButton) {
                // 发现不一样的按键按下，重新确认
                scanState = RECHECK_DOWN;
                DEBUG_PRINT("CONFIRMED_DOWN -> RECHECK_DOWN:%d\n", currPressedButton);
            }
            break;
        case RECHECK_UP:
            if (currPressedButton == ButtonNone) {
                // 确认没有按键按下，已经释放
                scanState = NO_DOWN;
                DEBUG_PRINT("RECHECK_UP -> NO_DOWN:%d\n", currPressedButton);
            } else if (currPressedButton != prePressedButton) {
                // 发现不一样的按键按下，重新确认是否按下
                scanState = RECHECK_DOWN;
                DEBUG_PRINT("RECHECK_UP -> scanState:%d\n", currPressedButton);
            } else if (currPressedButton == prePressedButton) {
                // 相同的键，噢，前一次可能是误触发，再次重新检查
                scanState = CONFIRMED_DOWN;
                DEBUG_PRINT("RECHECK_UP -> CONFIRMED_DOWN:%d\n", currPressedButton);
            }
            break;
    }

    // 记录当前结果
    prePressedButton = currPressedButton;
}

/**
 * 等待任意按键按下
 * @param id 按键是否按下的状态
 * @return 非0，有错误发生；0无错误
 */
uint32_t ButtonWaitPress (Button_Id * id) {
    uint32_t err = 0;

    err = xQueueReceive(button_queue, (void **)id, portMAX_DELAY);
    return err;
}

void button_taskEntry (void * param) {
    Button_Id buttonId;

    for (;;) {
        ButtonWaitPress(&buttonId);
        printf("Button press: %d\n", buttonId);
    }
}

/**
 * 按键初始化
 */
void ButtonInit (void) {
    for(int i=0; i< sizeof(gpio_nums)/sizeof(int); i++) {
        configure_gpio(gpio_nums[i]);
    }

    button_queue = xQueueCreate(10, sizeof(int));

    xTaskCreate(&button_taskEntry, "button_task", 1024*2, NULL, configMAX_PRIORITIES-10, &button_task);

    button_timer = xTimerCreate("button_timer", 20 / portTICK_PERIOD_MS, pdTRUE,
                                         NULL, button_timer_callback);

    xTimerStart(button_timer, 1);
}
